package raft

// must be used within critical section
func (rf *Raft) Convert2Leader(term int) {
	rf.Role = LEADER
	rf.currentTerm = term
	rf.votedFor = rf.me
	LastIndex, _ := rf.LastestEntryInfo()
	rf.nextIndex = make([]int, len(rf.peers))
	for i := 0; i < len(rf.nextIndex); i++ {
		rf.nextIndex[i] = LastIndex + 1
	}
	rf.matchIndex = make([]int, len(rf.peers))
	rf.persist()
	DPrintf("node[%d]: Convert To Leader ,term: %d", rf.me, term)
}

func (rf *Raft) doLeader() {
	if rf.killed() {
		return
	}
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.Role != LEADER {
		return
	}
	rf.CheckCommitIndex()
	if rf.IsTimeOut(rf.heartbeatTimeStamp, 150) {
		for i := 0; i < len(rf.peers); i++ {
			if i == rf.me {
				continue
			}
			go rf.doAppendEntries(i)
		}
		rf.heartbeatTimeStamp = rf.GetNow()
	}
}

func (rf *Raft) doAppendEntries(idx int) {
	rf.mu.Lock()
	if rf.Role != LEADER {
		rf.mu.Unlock()
		return
	}
	LastIndex, _ := rf.LastestEntryInfo()
	var splice []Entry
	prevLogIndex := rf.nextIndex[idx] - 1
	prevLogTerm := rf.log[prevLogIndex].Term
	if LastIndex >= rf.nextIndex[idx] {
		splice = rf.log[rf.nextIndex[idx]:]
	} else {
		splice = rf.log[0:0]
	}
	entries := make([]Entry, len(splice))
	copy(entries, splice)
	rpc_args := AppendEntriesArgs{rf.currentTerm, rf.me, prevLogIndex, prevLogTerm, entries, rf.commitIndex}
	rpc_reply := AppendEntriesReply{}
	rf.mu.Unlock()

	got_reply := rf.sendAppendEntries(idx, &rpc_args, &rpc_reply)
	if !got_reply {
		return
	}
	rf.mu.Lock()
	defer rf.mu.Unlock()
	if rf.currentTerm == rpc_args.Term {
		if rf.Role != LEADER {
			return
		}
		if rpc_reply.Term > rf.currentTerm {
			rf.Convert2Follower(rpc_reply.Term)
			return
		}
		if rpc_reply.Success {
			rf.nextIndex[idx] = LastIndex + 1
			rf.matchIndex[idx] = LastIndex
		} else {
			rf.quickBacktracking(idx, rpc_reply.ConflictTerm, rpc_reply.ConflictIndex)
		}
	}
}

func (rf *Raft) quickBacktracking(server int, conflictTerm int, conflictIndex int) {
	if conflictTerm == -1 {
		rf.nextIndex[server] = conflictIndex
		return
	}
	next := -1
	for i := len(rf.log) - 1; i >= 1; i-- {
		if rf.log[i].Term == conflictTerm {
			next = i + 1
			break
		}
	}
	if next == -1 {
		next = conflictIndex
	}
	rf.nextIndex[server] = next
}

//within critical section
//If there exists an N such that N > commitIndex, a majority
//of matchIndex[i] ≥ N, and log[N].term == currentTerm:
//set commitIndex = N
func (rf *Raft) CheckCommitIndex() {
	LastIndex, _ := rf.LastestEntryInfo()
	rf.nextIndex[rf.me] = LastIndex + 1
	rf.matchIndex[rf.me] = LastIndex
	N := rf.matchIndex[rf.me]
	oldCommitIdx := rf.commitIndex
	for {
		if N <= rf.commitIndex {
			break
		}
		count := 0
		for i := 0; i < len(rf.matchIndex); i++ {
			if rf.matchIndex[i] >= N && rf.log[N].Term == rf.currentTerm {
				count++
			}
		}
		if count > (len(rf.peers) / 2) {
			rf.commitIndex = N
			break
		} else {
			N--
		}
	}

	rf.applyToStateMachine(oldCommitIdx)
}
